package cloudbus

import (
	"zstackgo/pkg/header/managementnode"
	"zstackgo/pkg/utils/hash"
	"zstackgo/spring"
)

var (
	_ ResourceDestinationMaker                    = NewResourceDestinationMakerImpl()
	_ managementnode.ManagementNodeChangeListener = NewResourceDestinationMakerImpl()
)

func init() {
	o := new(ResourceDestinationMakerImpl)
	spring.Register(o, "ResourceDestinationMaker", nil)
}

type ResourceDestinationMakerImpl struct {
	nodeHash *hash.ConsistentHash
	nodes    map[string]*NodeInfo
}

func NewResourceDestinationMakerImpl() *ResourceDestinationMakerImpl {
	impl := &ResourceDestinationMakerImpl{
		nodeHash: hash.NewConsistentHash(hash.DefaultHashFn, 500, nil),
		nodes:    make(map[string]*NodeInfo, 2),
	}
	return impl
}

func (impl *ResourceDestinationMakerImpl) NodeJoin(inv managementnode.ManagementNodeInventory) {
	impl.nodeHash.Add(inv.Uuid)
	impl.nodes[inv.Uuid] = &NodeInfo{
		NodeUuid: inv.Uuid,
		NodeIp:   inv.Hostname,
	}
}

func (impl *ResourceDestinationMakerImpl) NodeLeft(inv managementnode.ManagementNodeInventory) {
	nodeId := inv.Uuid

	impl.nodeHash.Remove(nodeId)
	delete(impl.nodes, nodeId)
}

func (impl *ResourceDestinationMakerImpl) IAmDead(inv managementnode.ManagementNodeInventory) {
	nodeId := inv.Uuid

	impl.nodeHash.Remove(nodeId)
	delete(impl.nodes, nodeId)
}

func (impl *ResourceDestinationMakerImpl) IJoin(inv managementnode.ManagementNodeInventory) {
	nodeId := inv.Uuid

	impl.nodeHash.Remove(nodeId)
	delete(impl.nodes, nodeId)
}

func (impl *ResourceDestinationMakerImpl) MakeDestination(resourceUuid string) string {
	nodeUuid := impl.nodeHash.Get(resourceUuid)
	if nodeUuid == "" {
		panic("Cannot find any available management node to send message")
	}
	return nodeUuid
}

func (impl *ResourceDestinationMakerImpl) IsManagedByUs(resourceUuid string) bool {
	nodeUuid := impl.MakeDestination(resourceUuid)
	return nodeUuid == PlatformGetManagementServerId()
}

func (impl *ResourceDestinationMakerImpl) GetManagementNodesInHashRing() map[string]struct{} {
	return impl.nodeHash.Nodes
}

func (impl *ResourceDestinationMakerImpl) GetNodeInfo(nodeUuid string) *NodeInfo {
	info := impl.nodes[nodeUuid]
	if info == nil {
		// load node from db

		info = &NodeInfo{}
		impl.nodeHash.Add(nodeUuid)
		impl.nodes[nodeUuid] = info
	}

	return info
}

func (impl *ResourceDestinationMakerImpl) GetAllNodeInfo() []*NodeInfo {
	ls := make([]*NodeInfo, 0, len(impl.nodes))

	for _, v := range impl.nodes {
		ls = append(ls, v)
	}

	return ls
}

func (impl *ResourceDestinationMakerImpl) GetManagementNodeCount() int {
	return len(impl.nodes)
}

func (impl *ResourceDestinationMakerImpl) IsNodeInCircle(nodeId string) bool {
	return impl.nodeHash.HasNode(nodeId)
}
